File srcdescr.htm
Purpose Explaining the sourcecode
For Readers/users of the DemoGL sourcecode, DemoGL Developers.
Last changed 02-jun-2001
Last changed by Frans Bouma ([FB])


0. Contents.

  1. Preface.
  2. DemoGL Goals.
  3. DemoGL internals.
  4. The C vs C++ dilemma.
  5. Rules used inside the code.
  6. The DemoGL_DLL VC++ project.
  7. Small last things to think about.

Top

1. Preface.

In this document the inner workings of the library are explained, why they are set up the way they are, why the code is written the way it is now and how to interpret the sourcecode. This document is ment to be read from top to bottom, not as a reference. (But of course you could use it as a reference). This way you don't need to hop back and forth in the document to find the information you need. If you want to skip parts, that's fine, when you don't understand something later on, you probably skipped that part so you can find it back easily, just browse back in this document.

The goal of this document is to provide all the knowledge needed to work with the sourcecode of this library. It also states some hints on what you should do to keep your code fitting into the library. These hints can sometimes be hard to follow and you probably will just give them the finger and do it your way. Everybody is free in what to decide what to do with the code, that was one reason why the BSD license was chosen, but understand that an update of the library can generate conflicts with your new code that doesn't follow the rules stated below. When that happens you're facing the dilemma: "keep my old code but also the old functionality" vs "porting my additions to fit into the new library code". Tough question. Try to avoid it.

Top

2. DemoGL Goals.

The goals for DemoGL were and are still the same and give it a reason it's useful and needed and why it was build in the first place. It's likely that these goals will get adjusted during time, and will be removed when, over time, a goal is no longer an issue. When there are no goals left, the library is dead and the developers should do other things with their time. That's not a scentence ment to be funny but a reality check. Too much software is written and maintained that is already dead for years and should be burried and replaced with newer software that still has goals which are valid.

The goals:

  • It should provide a platform for developers so that all overhead required to create a framework that will be able to run the graphical effects is taken out of their hands and is done by DemoGL.

    --> reduce overhead.
    --> keep developers focussed on what they have to do.

    Met how?
    • DemoGL performs all the window, sound, I/O and OS related related actions
    • DemoGL lets developers run effect objects that are separate blocks, and those effects are the only codeblocks the developer has to write

  • It should make it easy for developers to make screensavers using their graphical effects, developed on top of the DemoGL platform.

    --> make difficult tasks easy.

    Met how?
    • DemoGL can run an application as a normal windows application or as a windows screensaver. The developer just has to add a few simple lines of code.

  • It should provide a black box to the developer so that machine specifics are not influencing the code the developer has to write. The developer should be able to write just 1 application that can run on a large list of resolutions, using graphic- card specific features or can do without them, use soundcard specific features or do without them, without including additional code to support these different target platform specifics.

    --> increase application flexibility and decrease usage of complex requirements.

    Met how?
    • DemoGL does all the resolution handling, display bitdepth handling, texture bitdepth handling, sound specific requirements handling, texture scale handling etc. The developer just programs effects using OpenGL depending, resolution independant arguments, and uses the soundcard independant soundsystem to perform sound.

  • It should provide an easy way designers, programmers and contentproviders can work together on one project and work on their own without having to consult others in the project team. It should be possible that programmers create functionality designers and contentproviders can use to build applications they want.

    --> increase team productivity.

    Met how?
    • DemoGL provides the team a set of tools and functionality to work together more succesful: a scripting system that controls the application flow and behaviour, a common used datafile system so contentproviders can add data easily, a range of functionality so developers can handle and content providers can supply data in a lot of different commonly used formats, sound AND images. Developers can easily program handlers that can be controlled by designers using the scripting system, so designers can actually design the application the way it was planned beforehand.

  • It should be easy for a developer to add the functionality the DemoGL Demosystem provides, plus the interface to DemoGL should be easy to understand and flexible to use.

    --> short learning curve and maximum flexibility when using DemoGL.

    Met how?
    • DemoGL is a library with functions and a simple baseclass. Including it into a project is done in seconds.
    • DemoGL's API is set up in a way that a lot of functionality can be done in 1 call, but also gives the developer enough room to set/adjust/tweak/use functionality so that numerous situations can be handled.
    • DemoGL comes with documentation that not only documents the complete interface but also documents the background of the library, limits and technical details, gives hints and tips and overviews of the different parts of the system.

  • It should not limit the developer in using the supported 3D API nor force a certain way of usage of the 3D API supported.

    --> abstraction of work without obstruction.

    Met how?
    • DemoGL uses OpenGL, but lets the user free how to use OpenGL. OpenGL is not incapsulated in an abstract layer nor limited by DemoGL's encapsulation of the OS dependant overhead.

These are the main goals set for DemoGL v1.3. Because every developer thinks different in ways to solve a problem, or better: to satisfy a goal set for the project, it's likely some readers of this document and the sourcecode will disagree with the list of how DemoGL v1.3 meets the goals set.

Top

3. DemoGL internals.

DemoGL relies internally on a couple of building blocks. These cornerstones of the system are the following:

  1. Application life phase
  2. System State
  3. Runtype
  4. Main message handler and the message pump
  5. Time-line
  6. Effectstore
  7. DemoDat internal data storage
  8. Texturestore


3.1. Application life phase.

When the application starts the DemoGL system by calling DEMOGL_AppRun(), the application and thus the system, will live through a couple of phases. These are semantic groups of system states, explained in ad 2. Below are the phases the application and the system will go through plus their short 2/3 character wide abbreviation. When DemoGL logs a line in the console it is prefixed with the abbreviation for the current phase.

Phase name Abbreviation
Pre-Boot Phase [PBP]
Boot Phase [BP ]
Application Initialisation Phase [AIP]
Application Run Phase [ARP]
Post Run Phase [PRP]
System Cleanup Phase [SCP]


As said, these phases are semantic, they are not physically stored inside the system. DemoGL uses system states to store the current state of the system. To know where in the lifetime of the application a certain state should be, these semantic phases were introduced, to illustrate the timespot of a system state in the application lifetime cycle (i.e. 1 run of the application)


3.2. System state.

DemoGL is a statemachine. When an application invokes DemoGL, f.e. via DEMOGL_AppRun(), the state machine starts and ticks from state to state when events occur (states complete, errors occur etc). Below are all currently defined system states, a description of the state and when the system moves on to another state. With every state the abbreviation for the phase the state belongs to is also mentioned.

Note: when an error occures during a state's execution or the user exits the application, the system will automatically invoke SSTATE_AEND_SYSTEMCLEANUP.

All system states:
SSTATE_PREBOOT
Phase: PBP
This is the startstate.

Will move to:
  • SSTATE_BOOT_STARTUPDIALOG when the DEMOGL_AppRun() call was of a normal runtype.
  • SSTATE_BOOT_MAINWNDCREATION when the DEMOGL_AppRun() call was of a NoDialog runtype or a normal/preview screensaver runtype

SSTATE_BOOT_STARTUPDIALOG
Phase: BP
When the startup dialog is opened, the state of the system moves to this state and stays in this state until the startup dialog is closed.

Will move to:
  • SSTATE_BOOT_MAINWNDCREATION when the user clicked "Ok" in the startup dialog.

SSTATE_BOOT_MAINWNDCREATION
Phase: BP
When the user clicks "Ok" in the startup dialog, the system will move towards the creation of the platform for the application to run on. To let the building block that pumps the life around inside the system, the main message handler, start to live, the window is created. This is done when the system is moved into this state.

Will move to:
  • SSTATE_BOOT_MESSAGEPUMPSTART when the window is created.

SSTATE_BOOT_MESSAGEPUMPSTART
Phase: BP
When the window and it's OpenGL rendercontext is created, the application is ready to receive messages. To receive and handle these, DemoGL dives into the message pump. From this state on, control lies with the message pump. Because the message pump can only live from the moment the window is up and running, we needed to handle state changes differently in the states before SSTATE_BOOT_MESSAGEPUMPSTART, i.e. more sequencial. When the message pump is up, messages will be send to let the system tick to other states. The message pump is called the KernelLoop.

Will move to:
  • SSTATE_BOOT_INITSTART when the window setup routine was successful and the message WM_DEMOGL_INITSYSTEM was sent and handled by the main message handler. When the main message handler doesn't receive this message the system will not continue.

SSTATE_BOOT_INITSTART
Phase: BP
Short state that signals the initialisation start of vital parts of DemoGL. Will set up the high resolution timer.

Will move to:
  • SSTATE_BOOT_SYSOBJECTSINIT when the high resolution timer is succesfully created and initialized.

SSTATE_BOOT_SYSOBJECTSINIT
Phase: BP
When this state is active the system objects of DemoGL are initialized, f.e. the console, the sound system etc. When these initialisations are completed the console is ready to display content and DemoGL spawns a thread (InitSystem()) that will continue the system object initialisation.

Will move to:
  • SSTATE_AINIT_SCRIPTPARSING when the InitSystem() thread spawned in the SSTATE_BOOT_SYSOBJECTSINIT phase succesfully completes its work. When the thread ends, it will send a WM_DEMOGL_INITAPP message that will result in the creation of another thread (InitApp()) that will do the application initialisation and thus will move into SSTATE_AINIT_SCRIPTPARSING at the start.

SSTATE_AINIT_SCRIPTPARSING
Phase: AIP
The InitApp() thread will first load and parse the application specific script which will result in the internal timeline event chain (in short: the timeline).

Will move to:
  • SSTATE_AINIT_EFFECTSINITwhen the script is succesfully loaded.

SSTATE_AINIT_EFFECTSINIT
Phase: AIP
When the InitApp() thread is done with the scriptparsing, it will initialize all registered effectobjects using their Init() method. When an error occured that would abort the system, this thread will post a WM_DEMOGL_ABORT message that will automatically abort the system, when handled by the main message handler.

Will move to:
  • SSTATE_AINIT_PRESTARTEVENTEXEC when the initialisation was succesful. This state is invoked in another workerthread, Prepare(), that is spawned when the message WM_DEMOGL_PREPARE is received by the main message handler, which is sent by the InitApp() thread when it succesfully completes its work.

SSTATE_AINIT_PRESTARTEVENTEXEC
Phase: AIP
When the Prepare() worker thread is spawned by the main message handler, that thread will tick the system into the state SSTATE_AINIT_PRESTARTEVENTEXEC, which mainly will execute all timeline events with a timespot below the starting point of the application, timespot 0. This workerthread will work in tandem with the main message handler (which is in fact our main thread). When a Prepare command is found in the timeline events to be executed, it will place that timeline event in a FIFO queue and will sent a message to the main message handler that an effect should be prepared. This is done this way because when an effect's Prepare method wants to upload textures to OpenGL it should be executed by a thread that owns the OpenGL rendercontext that will be used by the application. The Prepare() workerthread doesn't own that rendercontext, the main message handler thread does.

Will move to:
  • SSTATE_ARUN_KERNELLOOP when the Prepare() thread finishes it will send a WM_DEMOGL_STARTKERNELLOOP message which will cause a move to the SSTATE_ARUN_KERNELLOOP system state, once it's handled by the main message handler.

SSTATE_ARUN_KERNELLOOP
Phase: ARP
This is the state wherein the application will be when it's running as planned. Once the setup/boot states are all walked through, the application will find itself in this state and will be here as long as the application runs.

Will move to:
  • SSTATE_ARUN_POSTKERNELLOOP when the application ends by a script command or an unrecoverable error occurs or the user exits the application. This state is also illustrating the abandoning of the message pump. This state is invoked when the message pump is left, and thus the unavoidable termination of the application, and thus the DemoGL system is started.

SSTATE_ARUN_POSTKERNELLOOP
Phase: PRP
When the system is in this state, it's cleaning up kernel internal left overs and it's preparing the system to give control back to the application WinMain() routine. Once the cleanup is completed, control is passed back to the caller, the application WinMain() routine.

Will move to:
  • SSTATE_AEND_SYSTEMCLEANUP when the WinMain calls DEMOGL_AppEnd().

SSTATE_AEND_SYSTEMCLEANUP
Phase: SCP
In this state all system internal data is cleaned up, data the application has loaded and not freed is cleaned up and everything that was started by DemoGL is closed and freed. After this is completed, the control is again passed back to the caller, the WinMain routine of the application. The system is completely demolished internally after this state, the caller should end itself, by ending WinMain properly. That will result in a clean detach from the DemoGL DLL in core which will result in the last removal of internal objects from core memory.


3.3. Runtype.

DemoGL can run an application in several ways, each way is called a Runtype. Each Runtype needs it's own special threatment and actions so it will be easy for the library user to run his application as a certain Runtype. Once specified (as parameter to DEMOGL_AppRun()), the internal logic of DemoGL will test which Runtype it's in and behave like it. For example when the runtype is RUNTYPE_SAVER_NORMAL, pressing a key or moving the mouse will exit the application, because DemoGL thinks it's run as a screensaver.

Below are the defined runtypes inside DemoGL, their description and specifics.

RUNTYPE_NORMAL
This is the normal runtype. It will open a startup dialog and once the user clicks "Start!" the application runs as a normal win32 application, full screen or windowed. It ends when the pre-compiled script executes an exit command or the user closes the application. The developer of the application can tweak the settings available to the user of the application by pre-selecting options in the startup dialog, but the developer can't pre-select a set of start options the application should always use. The application runs standalone, without a parent.

RUNTYPE_NODIALOG
This is similar to RUNTYPE_NORMAL except that there is no startup dialog, the system starts where in RUNTYPE_NORMAL the closure of the startup dialog with clicking "Start!" would continue. The user is not able to select any option how the application should run. The developer can pre-select any option that is available by the startup dialog, and thus create his own startup dialog. The application runs standalone, without a parent.

RUNTYPE_SAVER_CONFIG
This is a screensaver runtype and is executed when you create a screensaver with DemoGL and you click "Settings" in the screensaver tab in Display Properties. The startup dialog is opened, allowing the user of the screensaver to pre-select all kinds of options how the screensaver should run. The selected options are stored in the registry and the application execution ends. A developer could also manually invoke this runtype after pre-testing if the user clicked "Settings" on the screensaver tab. This way the developer could first open a dialog that sets screensaver specific settings and with a button open the DemoGL screensaver config dialog to let the user set the display properties of the screensaver. The developer should take care of the settings he offers on the custom dialog, DemoGL takes care of the settings set on the config dialog.

RUNTYPE_SAVER_NORMAL
This is the normal screensaver runtype. When the screensaver is executed by windows because the delay set on the screensaver tab in Display Properties has passed, the screensaver is run using this runtype. The screensaver is ran like a normal application but it listens to more messages than a normal application. DemoGL will abort execution when the mouse is moved or a key is pressed. Just like a screensaver should do. The application runs standalone, without a parent.

RUNTYPE_SAVER_PREVIEW
This is the runtype that is used when the screensaver is run in the small window on the screensaver tab in Display Properties. It's basicly the same as RUNTYPE_NORMAL, except it can't exit by user interaction. The window runs as a child of the supplied window by Windows, which is the area on the screensaver tab.

RUNTYPE_SAVER_PASSWORD
This is the runtype when the user has ordered the screensaver to use password protection. Because only in windows95 and windows98 without pluspack this is an issue, this runtype is not further implemented. Under windows98SE, windowsME and windows2000 and future windows variants, the password protection is handled by the OS, so the screensaver itself doesn't have to check passwords.


3.4. Main message handler and the message pump.

The main message handler is the central routine where all messages received by the application are handled. DemoGL defines several custom windows messages, which are used to signal the occurance of several events. Together with the windows messages these messages have their own handler code inside the main message handler.

The message pump is the heart of the running application. Allthough very small, it drives the application by picking up all received messages from the message queue of the application window and make sure the message gets handled by directing the win32 API to deliver it to the main message handler. This is the reason it's called a pump: it pumps messages around.

The main message handler checks which message is to be handled and executes the handler code of that message. Not all messages received by DemoGL are handled by the main message handler, only the messages that have to be handled. Other messages are passed on to the win32 API for handling by windows itself.

The message pump is the routine where, when properly started, the application lives in until it is terminated. When no message has to be passed on to the handler using win32, the message pump directs the control first to the timeline event executor to execute any timeline events if these have to be executed according to the elapsed time since the start of the application and the timespot set with the timeline event (see also 3.5. Timeline), and after that to the render logic to render a frame. When that finishes, control is regained and the message queue is checked again. This way, DemoGL powered applications can still gain maximum speed and at the same time make it possible to handle messages send by the OS. This is important because win32 wants feedback for several messages send to applications: if a message is send and not handled by an application, some application related actions can put on hold if win32 doesn't receive a value back. When handling messages during execution of an application, DemoGL assures the developers that the application will be running without conflicts with the OS. A useful side effect of this is that parts of DemoGL, like the soundsystem, can send messages to the messagequeue which are then further handled by for example effects. Effects can also receive messages originally send to the application window, thus are able to react on mouse events for example, or send messages to other effects, using the main message handler and the message pump.


3.5. Timeline

The timeline is an abstract term for the bar on which all scripted events are placed, based on their timespot. All scripted events, i.e. command lines in the script that is accompanying every DemoGL powered application, are called Timeline events interally. A timeline event has a timespot, which is in fact the amount of milliseconds (ms) after the timeline origin, which is 0 ms, when the timeline event should be executed. A DemoGL application starts normally at the timeline origin, which is 0 ms. Because a developer can define timeline events with a timespot smaller than 0 ms, i.e. negative timespots, there are two kinds of timeline events: pre-execution timeline events and execution timeline events. Pre-execution timeline events are executed before the execution of the application starts. Execution timeline events are executed during the application execution. DemoGL uses an internal high resolution timer to calculate how many milliseconds have passed since a certain point in time. This way it's easy to determine when a certain timeline event should be executed: if the current spot in time, i.e. a certain amount of milliseconds after the timeline origin, is larger than the timespot set with a given timeline event. If that's the case, the timeline event is executed. This is done by a timeline event executor which interprets the command given with the timeline event and executes code to realise the functionality of the command. When a timeline event is executed, it's left in the timeline. This is done to support restarting of the application by a timeline event, for example a screensaver that should run over and over again.


3.6. Effect store

All effectobjects that are in the application and are used in the execution of the application, are registered with DemoGL before the control is passed on to DemoGL. These effectobjects are placed in the effect store. These store is the central point where effectobjects can be accessed by render logic or timeline event execution code. When an effectobject has to be accessed, it will be looked up in the store, and the object is then handled as a single object. All effectobjects stay in the effectstore until a timeline event is executed that will remove the effectobject from DemoGL and thus from the effectstore.


3.7. DemoDat internal data storage

DemoGL uses internally all kinds of variables and objects. Some of them have global system scope, others have a smaller scope. For the variables that are not stored into objects nor are defined in cpp files and have then filescope, a central object is defined to avoid a lot of global variables. This object is the DemoDat object. DemoDat contains not all data which has global scope. Some data stores or objects are not stored inside the DemoDat object for performance reasons: references to the soundsystem, the timeline store etc. Later in this document the C-C++ tradeoffs are discussed, the DemoDat vs some global declarations of some data is one of the topics.


3.8. Texture store

The texture store is the central storage where all the created textures are stored. It offers basic texture management facilities and texture sharing among effectobjects. The texture store stores texture objects, which are created when the developer loads a texture from disk or creates a texture using framebuffer data or a supplied buffer. With the texturestore are defined all the functions needed to manage the textureobjects, upload and unupload them with OpenGL, load textures with different kinds of formats from disk etc.

Top

4. The C vs C++ dilemma.

DemoGL was started as a functionset for GLUT, a C library with basic functionality to run an effect using OpenGL. After some weeks, GLUT was replaced with own code to setup and do OS specific functions. It's this start that makes DemoGL also a library that has legacy code in it, like many others. Because DemoGL is a library that is a functionset, an API, it was not easy to design DemoGL using plain C++, thus making DemoGL in fact an object that was instantiated and what spawned other objects when requested. Because DemoGL was kept closed source, it was harder to make it work only by using C++ than it would be when a hybrid was chosen between C and C++: where function-oriented programming was done, C was chosen, where data-oriented programming was done, C++ was chosen. When you'll read the code, and you're a die hard C++ fan, you'll probably wonder why there isn't more C++ specific code inside the library. This is partly due to the fact that the main developer, Frans Bouma, was mainly a C programmer, not a C++ programmer. Because the library, being an add-on API, contains a lot function oriented code, it was easier to go for C, because that was proven and known territory, while C++ was new. Still, newer parts are C++ only, like the soundsystem.

It will always be a discussion when and why to choose a function oriented programming model or a data oriented programming model. Fact is, that code will be more readable by more people when it's kept simple, and written in a language that fits the algoritm it forms. Using plain C in the function oriented parts was and still is a good choice. It fits better with the win32 API, DemoGL's function oriented API and the C oriented OpenGL API.

When you'll read the code, keep that in mind. No matter how today's software industry finds C++ better than C, it's not. It's just a different language, usable in a different situation. Therefor, situations where data oriented code was at stake, C++ is used, except in some legacy parts of DemoGL. Examples are the texturemanager functionality, the timeline functionality and the effectstore functionality. These data stores are made using objects, but the store that contains these objects is not an object, but just a datastructure and a set of functions, forming the functionality. These storages plus functionality could be transformed to C++ classes with the functions as methods. Nothing would change though. No extra functionality would be created, though some conflicts would arise. Because of the C-nature of OpenGL and the win32 API, functions targeting those API's are not data oriented, but just functions in a function oriented piece of code. Placing these as methods of the store-classes is wrong. So even with these classes, you'll keep functions who won't fit in and keep parts function oriented and thus C-based.

Another, not often discussed topic, is the difference between C++ and C: the C preprocessor. C++ should do without it, according to Straustrupp, but you won't find any const or template definition in DemoGL, only #defines and typedefs. It was decided it was making the code more readable by more people if the code was setup using #defines, structs, unions and typedefs instead of const, class templates etc. Of course where classes are used instead of structs, classes are defined, not structs.

This small C vs C++ topic is not ment to heaten up a discussion between the two languages. It's ment as a guide to understand why DemoGL contains some functionality written in plain C and other functionality written in C++. If are offended by the way some functionality is written in DemoGL, don't be. Even if we re-read code we've put in the library a year ago we are offended sometimes :). We did everything we could to produce nice, clean, easy to read code that is well documented, well written, extensible and without bugs. To do this best is to use the right tool for the job. Sometimes

Top

5. Rules used inside the code.

Several rules were used during DemoGL development. It's recommended you follow these rules when you want to add code to the library or want to let us add your additions to the codebase. Some are basic coding practises, others are not that common and can be sounding a little over the top. These rules are very important when you want to contribute code to the codebase.

Programming rules

Header file usage
  • Every .cpp file should have a comparing header file, .h. All declarations, defines, typedefs etc are made in that headerfile.
  • No #defines, class definitions, struct definitions and declarations of types are allowed in .cpp files. Use the .h file for that. Exceptions are #defines of macro's, however try to avoid macro's.
  • Preferably include header files in a global include file so it can be pre-compiled, plus this solves include loop errors.
  • Sections in the headerfile should be properly separated by commentblocks.
  • Place only headerfile information of related material in the same headerfile.
  • Name the headerfile after the .cpp name.
  • Sort class methods ascending.
  • Always use a #ifndef _HEADERNAME #define _HEADERNAME #endif construction to avoid multiple includes of the same file.

Source file usage
  • Don't put code in headerfiles, no matter how small the code is, even in class definitions. Place code in .cpp files, the source files.
  • Sections in the sourcefile should be properly separated by commentblocks.
  • Place only code related to eachother in one single .cpp file. Split up files if code is not tight related.
  • Choose a good name for the .cpp file, related to the code inside. Prefix every filename with 'dgl_dll'.

Classes
  • No public members are allowed. Use only private members. When you need a fast 'object' with just variables, use a struct.
  • No initialisation of members is allowed in the constructor declaration. Initialise members inside the constructor body. This way it's more extensible, and more readable because you can add comments.
  • Keep Get/Set methods related to the values they get/set.
  • Use classes when you want to code a topic that is best served with OO programming. Don't use classes when a functional approach would be better.
  • Always implement virtual functions in base classes.

Comments
  • Add lots of comments and keep them accurate and describing. A reader can interpret what for() does, not why it's there and why it's constructed that way.
  • Every method/function should have a commentheader that describes what the function/method does, even if the method is only one or two lines big. If possible, describe the parameters sent and returnvalues possible. If possible describe key functionality so the reader of the code is not overwhelmed by the code.
  • Add comments with preprocessor directives.

Functions / methods
  • Functions and methods can have multiple exit points. Some people think multiple exit points in a function is bad, but opinions differ on this. A function/method should eliminate cases when the caller shouldn't be executing the core functionality of the function/method. Always ask you the question: should the caller still be in this function? if not: exit.
  • Functions and methods should follow the following order of codeblocks:

    • Function/method header
    • Local variable declaration
    • Local variable initialisation
    • codeblocks

    This way you always know where to look when a local variable is not initialized etc. Of course it's not always necessary to initialise variables at the top of the function/method. But it keeps logical blocks together in a function so less puzzling has to be done when a reader wants to find out what's going on.

  • No block locals are allowed. Always declare your variables with function scope, not block scope inside your function/methods.
  • No direct initialisation of variables is allowed. First declare, then initialize. This way you can keep logical parts of a function separated from each other, even if this is slightly less efficient.
  • Don't return pointers to local declared memory blocks. Concern the following:

    
    	{
    		char	sFoo[MAX_STRING_LENGTH];
    
    		sprintf(sFoo,"bla");
    		...
    		return sFoo;
    	}
    				
    sFoo is declared on the stack in the stackframe of this method/function. Returning it to the caller will cause trouble, when the caller stores this pointer in another variable. Always return pointers to allocated memory.
  • Copy structures which are passed by reference into local structs when using the data, so caller always frees the data passed, and is responsible for the data passed, never the callee. People who write COM objects are used to this.
  • Keep methods/functions atomic in error handling. Allthough it seems nice to report errors back to the caller, you don't know which method/function will call you in the future, don't create a system that depends on the caller. Report success/failed signals, using SYS_OK and SYS_NOK flags, like in COM and eventually an errorcode if necessary, but keep errorcode general and not caller specific.
  • It's not always bad to keep a long function in tact, as long as the code is generic and just a lot of the same. DemoGL contains a few long, but boring functions. Not a lot is going on, just a lot of the same. Message handlers are longer than you'd expect and the timeline event execution routine is very long. The functions could have been split up in a lot of smaller functions, but that would create extra overhead and not that much more readability, due to the fact that the overview of what's going on is gone when using a lot of small, albeit the same, functions who are only used by one other function. It's wise to split up functions that tend to do a lot of work which is not a repetitive collection of the same actions over and over again. You might now think that there is no loop whatsoever in the source but the code I'm refering to is a long list of command handler routines, which are semantically a lot alike but differ in syntax and errorreporting.
  • Place the type of the returnvalue of a function/method on the line above the method/function header.

    Example:

    
    	char
    	*CClass::Foo(int iBar)
    	{
    		// some code
    		return pszBla;
    	}
    				
  • Place function header declarations in the headerfile and extern functionheaders which should be included in more than one .cpp file.
  • Don't use extern "C" {} when exporting API functions. The API functions exported that way are v1.2 functions, and declared that way for backwards compatibility.
  • Never return a pointer to a block of memory to a caller that is not inside the library, and there is no free function. Because memory allocated inside the dll cannot be freed by code outside the dll, you can't pass back pointers to memory, allocated inside the dll, that should be cleaned up by the caller, outside the dll. Include a mechanism to free the memory when doing so, as is done with the filepointer return/freeing in the DemoGL API.

Statement layout
  • It's not bad to include more curly brackets than official necessary. This will increase readability and excludes codingscheme related assumptions.

    Example:

    
    	if(foo)
    		bla();
    				
    vs.
    
    	if(foo)
    	{
    		bla();
    	}
    				
    The latter is more readable, plus allowes a person to type comments more easily and add other statements more easily. Less obvious, but also helpful are curly brackets with case: clauses.:

    
    	switch(foo)
    	{
    		case BLA1:
    			// some code
    			break;
    		case BLA2:
    			// some more code
    			break;
    	}
    				
    vs.
    
    	switch(foo)
    	{
    		case BLA1:
    		{
    			// some code
    		}; break;
    		case BLA2:
    		{
    			// some more code
    		}; break;
    	}
    				
    It will be a matter of taste in most cases, but keep in mind that if you want to contribute code to the library it should fit in the code that is already there.
  • Place the start curly bracket of a block on the next line.

    Example:

    
    	if(foo) {
    		bla();
    	}
    				
    vs.
    
    	if(foo)
    	{
    		bla();
    	}
    				
    This seems like a small deal but it isn't. This issue is dividing C programmers into 2 groups that hardly mix.
  • Indent the code. Use the TAB key to indent the lines. Always indent when possible.
  • Use the defined naming scheme whenever possible.
  • Use typedef when possible.
  • Do not reuse constants, even when they have the same value as what you want to return, when the name of the constant is not related to what you semantically are returning.

Naming scheme
The code is written using a namingscheme for all variables, class definitions, structs and constants. This scheme is adjusted a bit over time, so some inconsistencies can still be in the sourcecode. Be sure you adapt this coding scheme to all your code you want to add to the library. It might be a hell at first when using this coding scheme, but after a while it will pay off. Of course this naming scheme can be less good than the one you probably use yourself for ages, however it's not the quality of the naming scheme that matters, but the presence of it. Too much code is written without any naming scheme and for Open Source software it's very important to keep one consistent naming scheme that is mandatory for all developers. This scheme is by no means complete. If you think it should be adjusted or it lacks some definitions, step forward and suggest the addition/modification.

The following naming scheme is defined. If you wonder why variables and code is written the way it is, you'll find your anwsers probably here. It's a subset of Hungarian coding, with modifications.

  • #defines are capitalized. Example: REGKEY_CURRENTUSER_SUB
  • API methods are prefixed with DEMOGL_. Example: DEMOGL_ConsoleLogLineV()
  • API functionnames should be build up like: DEMOGL_[objectaffected][action]. Example: DEMOGL_TextureLoad().
  • API constants are prefixed with DGL_. Example: DGL_VF_VSYNC
  • Class names are prefixed with 'C'. Example: CEffect
  • Struct names are prefixed with 'S'. Example: SSoundElement3DAttributes
  • Enum names are prefixed with 'E'. Example: ECmdToken
  • Class en struct member variables are prefixed with 'm_'. Example: m_iToken
  • Global (library scope) variables are prefixed with 'm_g'. Example: m_gpDemoDat
  • All member variables and local variables are prefixed with a character string in lowercase reflecting the type. This prefix is placed after the member variable prefix 'm_' and after the global prefix 'm_g'. The list of type prefixes is:

    • 'i' for integer. Example: int iCounter;
    • 'f' for float. Example: float fX;
    • 'dw' for DWORD. Example: DWORD dwBassFlags;
    • 'l' for long. Example: long lFileLength;
    • 'p' for pointer. Example: char *pCurrent;
    • 's' for charstring. Example: char sFoo[MAX_STRING_LENGTH];
    • 'psz' for pointer to zero terminated charstring. Example: char *pszName;
    • 'b' for boolean. Example: bool bHasTexture;
    • 'h' for handle. Example: HWND hWnd;
    • 'by' for byte. Example: byte m_byKey1;
    • 'cs' for Critical Section Mutex. Example: CRITICAL_SECTION m_csSystemStateMutex;

    There are legacy declarations of pointers to strings that should be declared as psz's instead of just 'p's. Besides these definitions, you can combine them. For example a pointer to integer can be declared as 'piFoo', instead of just 'p'. You will also notice the several custom types declared in DemoGL's sourcecode. For example the channel objects in the soundsystem are prefixed with 'ch'. Most arrays, when declared local or as member, are prefixed with 'arr'. An array of channelobjects which is a member will then become: m_arrchFoo[MAXVALUES].

  • All names of membervariables and other variables should start with a capitol character after the prefixes. Also put a capitol at every wordstart in the variable name. Thus: m_csSystemStateMutex, instead of m_csSystemstatemutex.
  • Avoid understores in variable names and function/methodnames. Use them only as separator to exclude parts in the name.
  • Use C++ commenting syntax when possible.
  • In API functionheaders, define variables as 'const' when possible. Keep in mind that caller should be able to modify objects passed to a function.
  • Never use hardcoded values in the code. Always define a constant using the right scheme.
  • Adjust the scheme, don't alter it. Custom types ask for custom prefixes. Add these prefixes to the scheme, like a pointer to a SSoundElement3DAttributes struct should be called: m_psea3DAttributes. Keep these adjustments consistent, that's the reason why they should be added to the scheme when used.
  • Dont include type info in the name. Because the prefix already tells the reader the type of the variable, it's not necessary to include also words like 'String' or 'Str' as the 's' or 'psz' will already indicate it's a string.
  • Keep names of variables descriptive of what they contain. If possible, always make the name as descriptive as can be. 'bFlag' is nice, but what kind of flag?
  • Keep method and functionnames descriptive of what they do. A reader should understand what a function or method does when he sees the name of the method/function. If the function does more than that, it should be split up or the name should change.
  • Don't be afraid of long names. Some people avoid long variablenames and end up using complete unworkable names for variables. One of the longes names used is: m_iDeltaVolumeOutsideOuterProjectionCone, a member of SSoundElement3DAttributes. Making this name shorter wouldn't tell the reader what's inside the name.
  • Use i, j, k and l for loopcounters. By unwritten definition, in C and C++, loopcounters in for loops and whileloops are named 'i' and nested loops use variablenames one larger as the parent loop, like 'j' or 'k'.

Internal functionality Usage
"Eat your own dogfood!". Well, when possible. When you have the choice to call a library API method or an internal function that is called by the API method, choose the API method. This way you keep the internal code more bugfree. The API is not going to change that often, if it will ever change, code that relies on internal functions will probably become unstable and buggy when the internal workings of an API method will change (i.e.: how the API method works. It still provides the same functionality). You also have the possibility to test API code right away.

DemoGL contains some handy pieces of code that are very useable for developers of the library and need explanation when you want to read and/or modify the code. Below is a list of codepieces and what they do. Some are 3rd party, some are developed from scratch for DemoGL. Use these as much as possible instead of your own code. These codepieces are tested thouroughly. When a piece of code is limited to your needs, adjust it. Don't include another piece of code that does almost the same but something more, adjust the current codepiece.

  • CStdString. This is a 3rd party stringclass written by Joe O'Leary. It's used often inside DemoGL and it should be used instead of char sFoo[value] when possible. There are several occasions when CStdString is not useful, for example when exporting a struct with a char buffer to a caller outside the library. CStdString is a C++ class similar to CString in MFC but more efficient.
  • Low level debugger. DemoGL contains it's own low level debugger. When you include the _DGLDEBUG constant in the C++ preprocessor definitions in Project Settings, code will be generated using the low level debugger functions. The low level debugger will open a file and write strings to that file when ordered. The open/close functionality is included in the DllMain function, but commented out. If you want to debug parts of the library and you want to use the low level debugger, uncomment these lines and call the low level debug function of your needs. This low level debugger can be adjusted with a console instead of a file logger. The win32 API contains code to open a console window for logging purposes. The low level debugger is not thread safe at the moment but flushes data directly to disk when ordered to write data to the file. Keep in mind when using CreateThread() instead of the _beginthreadex() calls as DemoGL uses now, the CRT functions used in the low level debugger can be thread unsafe and can cause corruption of data. See CreateThread() documentation for details.
  • CSysConsole. DemoGL contains a console for debugging purposes. You can log data to the console, which can scroll back and forth inside a buffer of userdefinable length and width, or retrieve system info like timeline status, running effects etc. Use the API functions DEMOGL_ConsoleLogLine() and DEMOGL_ConsoleLogLineV() to log debuginformation in the console. This method is very fast, as the low level debugger is not. The console debugging method can be a little tricky when you are debugging a screensaver you wrote with DemoGL, because any keypress will exit DemoGL when running as a screensaver. The systemconsole contains also the debugoverlay that is useable during runtime which displays a subset of the contents of the console. DemoGL uses the console to log systeminformation. When you specify QUICKBOOT as true when starting your application, DemoGL will however only log errors, and no system information is logged.

Top

6. The DemoGL_DLL VC++ project.

The project contains several directories in the project fileview explorer, which we will call 'folders' to avoid conflicts with 'directory' on disk. The folders are:

  • Source Files. This folder contains all .cpp files
  • Header Files. This folder contains all .h files
  • Resource Files. This folder contains all files included as resources in the library. See the Resources Tab.
  • Libs. This folder contains all .lib files linked with the library. This is done so a change of a lib to a newer version will cause a recompile of the library.
  • Source docs. This folder contains all documentation about the sourcecode
  • External Dependencies. This folder is added by VC++ and contains all files needed for compilation.

The folders 'Source Files' and 'Header Files' contain besides the DemoGL sourcefiles, also a '3rdParty_H' (in Header Files) and '3rdParty_CPP' (in Source Files), folder, which contain the 3rd party code included in DemoGL. All code inside these folders is written by others and contains and should keep the names of the authors.

On disk, the directory structure is slightly different. In the rootdirectory of the project are the necessary project files for VC++, a makefile plus several directories with the files. Below is a list with eventually subdirs and what they contain:

  • CPP. The root directory for the directories that contain the .cpp files. This directory contains the following directories:
    • DemoGL, where all .cpp files written for DemoGL are placed.
    • Misc, where all 3rd party .cpp files are placed.

  • Debug. The directory where the result of a 'debug' build is placed by VC++.
  • Files needed for distribution. In here are the files needed for the SDK. These files are not included in the project, but are necessary to run DemoGL applications.
  • Include. The root directory for the directories that contain the .h files. This directory contains the following directories:
    • DemoGL, where all .h files written for DemoGL are placed.
    • Distribution, where all the .h files, needed for the SDK are placed, like DemoGL_DLL.h etc.
    • JpegLib, where all files for the jpeglib are placed. Could be moved to Misc.
    • Misc, where all 3rd party .h files are placed.

  • Libs. The directory for all .lib files needed to build the DemoGL library.
  • Release. The directory where the result of a 'release' build is placed by VC++.
  • ReleaseVC50. The directory where the result of a 'WIn32 VC50' build is placed by VC++. VC++ 6.0 has the feature to build .lib import libraries for VC++ 5.0. If you want to build an importlib for VC++ 5.0, build the 'Win32 VC50' build using 'Set active configuration' in VC++ in the Build menu.
  • Resources. The directory where all files included as resources are stored.
  • Source docs. The directory where the documentation of the source code is stored.

6.1. Resources
DemoGL contains several resources. These resources are dialog templates and binary files. Select the 'Resources' tab in the project explorer inside VC++ to view the list of resources available. These resources are stored in resources.rc, the file that will be compiled by VC++ which will result in a binary blob that will be linked with the DLL. In resource.h, the controls used in the dialogs are defined with #defines. This file is generated by VC++ and altered to define two standard values for two elements: IDI_ICON, the application's icon, and IDB_APPLOGO, the grey bar at the left of the startup dialog which can be customized to a provided picture. These two elements have standarized values and shouldn't be altered.

The resources contained in DemoGL are the following:

  • "JPG". This is a custom resourcetype and contains one object, a lensflare in JPG format, which is used in the About dialog. Total size is 13KB.
  • "ZIPPEDTGA". This is a custom resourcetype and contains one object, a 'compressed' TGA image of the DemoGL logo. This TGA image contains an alpha channel and is used in the About dialog. It's compressed with ZLib to keep the size small. Total size is 11.4KB.
  • Bitmap. This resourcetype contains one object, a bitmap that is placed as the application logo in the grey bar when the application itself isn't providing a resource with ID IDB_APPLOGO (i.e. 140). Total size is 5KB.
  • Dialog. This resourcetype contains two objects: the About dialog template and the Startup dialog template.
  • Icon. This resourcetype contains one object, the default Icon for the application if the application itself isn't providing a valid icon with ID IDI_ICON (i.e. 110).

Some will now argue that 13 + 11.4 + 5 = 29.4KB could have been saved by eliminating the image resources. These resources are included for the About dialog to show off something special. The 5KB for the bitmap in the applogo image holder is needed otherwise the control will not show up in the dialog template.

6.2. File descriptions
Below are descriptions of all the files in the project written for DemoGL, the dgl_dll*.* files. Each file contains a short description of its contents plus some key information about the logic of the code stored in the file. These descriptions are by no means an intention to be an in depth view of how every detail works. The code contains enough comments and the code itself is set up as clean as possible so these descriptions are helping you where to take off when reading the sourcecode.

6.2.1. Source Files.
dgl_dllbootutilfuncs.cpp
This file contains all functions that are doing several blocks of the booting process of a DemoGL powered application. Among the several OpenGL window setup related routines are also the decrypting routines to decrypt data crypted with Cryde. No extra information is needed, every routine is explained in detail in the sourcecode.
dgl_dlldemodat.cpp
This file contains the implementation of the CDemoDat class. Because this class is mainly an internal datastore, it's quite big with a lot of small get/set methodcombinations. It's wise to use the File Outline functionality of VC++ 6.0 in this file. (browse toolbar). Keep an eye on the ticker routines, which are stored here and set up the system high resolution timer, which is kept inside CDemoDat, plus the overlay switch on/off routines. When a user toggles an overlay (console, debug overlay, FPS counter), the console has to clean the buffer using no scissoring otherwise a demo with scissoring will contain flickering elements of the overlay.
dgl_dlleffectbase.cpp
This file contains the base class implementation of the CEffect class which is used by applications to derive their effect classes from and is used internally as reference class to access effectobjects. The class itself only contains implementations of the virtual functions.
dgl_dlleffectstore.cpp
This file contains the implementation of the CEffectStore class. This class is used to store an effect object reference plus related information inside DemoGL. When an application calls DEMOGL_EffectRegister(), a new instance of CEffectStore is created and filled with the information provided. CEffectStore objects are stored as a single linked list with a head and tail pointer, stored and defined in dgl_dllmain.cpp.
dgl_dllendsystem.cpp
This file contains the code that is executed to clean up all the left overs when DEMOGL_AppRun() returns. The routine cleans up all data allocated after DEMOGL_AppRun() was called. Data allocated in DllMain() is not deallocated because that is deallocated when the process itself detaches itself from the library, see DllMain() in dgl_dllmain.cpp for details.
dgl_dllextensions.cpp
This file contains the implementation of the CExtensions class. This class contains at runtime booleans for a long list of extensions, reflecting their presence in the ICD used.
dgl_dllfifoqueue.cpp
This file contains the implementation of the CFifo queue class. This class implements a first-in-first-out queue for CEffect references. This queue is used by the Prepare thread when it finds an effect has to be prepared. Because in the Prepare thread, the OpenGL rendercontext is not available so calling these effect's Prepare() method is useless or will render the code inside that method useless when it does something with OpenGL, so the Prepare() thread places the effect in the Fifo queue instead. The actual placement of the effect inside the queue is done inside DoExecuteTimelineEvent(), when it's called by the Prepare thread. When DoExecuteTimeLineEvent places an effect inside the fifo queue, it will send a message, WM_DEMOGL_PREPAREEFFECT, to the main thread. The main thread will, when receiving the message, pick up the stored effects in the fifo prepare queue and will actually call the Prepare() methods of the effects stored in the queue. The queue has a fixed length of 100 slots which should be more than enough, when the main thread and the Prepare thread work in tandem and get equal amount of CPU time.
dgl_dllguicontrol.cpp
This file contains the implementation of the base class CGuiControl that is useable as a baseclass to derive guicontrols from, like the progressbar is a derived class from CGuiControl. The class doesn't contain any render logic, just data and get/set pairs.
dgl_dllkernel.cpp
This file contains the core control functions of the system when it's running and starting. In this file you'll find the Main message handler and the message pump, the effect register routine and the frame render routines. The message pump is called 'KernelLoop()'. The main message handler is called 'MainMsgHandler()'. These two routines are key elements in the system. The MainMsgHandler routine contains key code how the system state is controlled and how to react on external- or internal generated events. The frame render routines are RenderFrame() and RenderLayerFrames(). RenderFrame is the main routine which controls rendering of frames. It's called by KernelLoop whenever possible, to get the highest framerate possible. RenderFrame decides what to render: console or effects. When effects have to be rendered it calls RenderLayerFrames() to render all effects on all layers using the effects's RenderFrame() methods.
dgl_dlllayer.cpp
This file contains the implementation of the CLayer class. This class contains all data of a layer in DemoGL. When an effect is started on a layer, an instance of the CLayer class is created to represent that layer. All layer objects are stored in a double linked list with a head pointer, stored and defined in dgl_dllmain.cpp
dgl_dlllayerutilfuncs.cpp
This file contains all utility functions that work on the layer store. All layer store manager functions are stored in this file. Also the per layer object functions are stored here like hide or show a layer. The tricky code is in the double linked list logic.
dgl_dlllowleveldebugger.cpp
This file contains the low level debugger implemented in the library. The low level debugger is a set of utility functions that work with a logfile. This logfile is initially opened in the DllMain() function in dgl_main.cpp. When you uncomment the open/close calls of the logfile, DemoGL will log data in the logfile which is placed in the current directory of the application. You can call a utilty function of the low level debugger from anywhere in the library code. Be sure to include _DGLDEBUG with the preprocessor directives in the project settings: when _DGLDEBUG is defined, all functions of the low level debugger have functionbodies and will thus be executed when called. When you include calls to the low level debugger functions in the library source code and _DGLDEBUG is not specified with the build, for example with a release build, the calls to the low level debugger will be calls to empty function bodies and will be removed by the optimizer in VC++. The low level debugger contains functions to convert a window message to text, log strings and other miscellaneous functions that come in handy when doing logging during runtime.
dgl_dllmain.cpp
This file contains one function, the DllMain() function. This function is the entrance function for win32 to attach the DLL to the process or thread that loaded the library, directly or indirectly. DllMain() contains code for the four situations that this function can be called: attach to thread or process or detach from thread or process. Only attachment and detachment to a process is implemented, since this is what we want: an application using DemoGL is loading the DemoGL DLL, most likely by VC++ generated stubcode, and will attach to it after the DLL is loaded. When an attachment happens, the DLL can do initial setup of objects and internal structures. DemoGL will create it's internal objects and stores and do very basic initialisation. After the application ends, thus after the WinMain() function is exited, DllMain() is called again, now for the detachment from the application process. DemoGL will then remove the internal structures and objects created in the attachment handler. After that call there shouldn't be any memory left that is still allocated. When you monitor memory leaks using the win32 heap monitor functions be aware that when DEMOGL_AppEnd() is finished the memory occupied by the elements allocated in the attachment of the library is still not freed and your memleak sniffers will alert for memory leaks. You should place your sniffers after the detachment code in DllMain() to have accurate memleak sniffing. This file contains besides DllMain() also the declaration of all the objects and structures that have library scope. Place all library scope variable declarations in this file.
dgl_dllprogressbar.cpp
This file contains the implementation of the CProgressBar class. This class is derived from CGuiControl and the instance of this class is used as the progress bar during boot/initialisation. DemoGL contains one progress bar object, declared in DllMain(). It's declared with global scope to have any eventhandler in the system be able to make the bar move forward when a certain task is completed. CProgressBar implements several visual types. It uses a mutex to protect it's internal value it reflects in the bar for mutations by multiple threads. CProgressBar contains its own rendercode and this code is called by the system console. Because the system console is an object on itself, it is not relying on system wide global variables and gets a reference to the instance of the CProgressBar class when it's initialized. This is a slight inconsistency when compared to the CDemoDat situation which is referenced throughout the system, also from the SoundSystem object.
dgl_dllscriptfuncs.cpp
This file contains all script related functions. In this file are stored the lexical analyzer and the token parser plus utility functions to ease parsing of the script. The code uses a token array with token objects which are filled by the lexical analyzer Lex(). When Lex() has tokenized a line, this line is parsed and if possible transformed into a timeline event and stored in the timeline at the right spot. The lex-parser tandem is very basic, for example the parser doesn't do any syntax checking, this is done at runtime.
dgl_dllsoundsystem.cpp
This file contains the implementation of the CSoundSystem class, the CSoundChannel class, the CSoundElement class, the CMP3Element class, the CMODElement class and the CSampleElement class. Besides these classes it contains all soundsystem related utility functions and initialisation functions. DemoGL 1.3 uses BASS to produce sound and the soundsystem is written to work as good as possible with BASS. To the outside world the soundsystem is a black box and the code that uses the soundsystem is not aware BASS is used. This way other libraries, besides BASS, can be used to power the soundsystem if BASS doesn't fulfil your needs. DllMain() will create an instance of CSoundSystem so DemoGL can produce sound using this instance. Internally the sound system uses sound elements. Sound elements are instances of CMP3Element, CSampleElement or CMODElement. These three classes are derived from the CSoundElement, which is the base class for sound elements inside the sound system. These three classes contain the sound data of their type, which is MP3, Sample (wave) or MOD/S3M/IT/XM data. All interaction on these elements is done by the sound system object. Inside the sound system, all sound elements are stored in the sound element store. When a sound element is started, thus it is playing, an instance of the CSoundChannel class is created. This instance is placed in the sound channel store. This sound channel object is the controlling instance of the played sound. Thus, CSoundElement derived classes control the to be played sounddata, the CSoundChannel class controls the playing sounddata.
dgl_dllstartsystem.cpp
This file contains the startroutines needed to start the application. It contains DEMOGL_AppRun() and related routines plus the initialisation and prepare routines of the system which are called or started as a thread in the several phases prior to the SSTATE_ARUN_KERNELLOOP state. DemoGL_AppRun calls routines that actually do the startup of the application depending on the runtype DemoGL should run in. Depending on the runtype, the routine starts a startup dialog, opens the main window and dives into the kernelloop. Keep in mind that InitApp(), InitSystem() and Prepare() are started as threads by messagehandlers in MainMsgHandler() in dgl_dllkernel.cpp, to activate a state.
dgl_dllstartupdialog.cpp
This file contains the code for the startup/screensaver config dialog and the about dialog. The startup/screenaver config dialog will read the settings from the registry if present, and fill the dialog with the settings provided by the developer. It exits, filling the startup dat structure with the settings set and eventually calling the routine which stores the settings in the registry. See the messagehandler of this dialog for details. The about dialog contains an OpenGL canvas and rotates a quad with a DemoGL texture and is envmapped with a flare to give it some shine.
dgl_dllsysconsole.cpp
This file contains the implementation of the system console class CSysConsole. An instance of this console is created in DllMain() in dgl_dllmain.cpp. The CSysConsole class controlls the console buffer, the rendering of the console buffer, the header above the console buffer when the console is displayed, the rendering of the input prompt, the rendering of the FPS overlay and the rendering of the debug overlay. The CSysConsole uses two textures with a font to render the characters using two vertexarrays for every font texture one. There are two textures to have a 512x256 wide texture and still keep the details on voodoo3's. The CSysConsole class does not contain the contents of the input prompt, it only renders it. This is because the datainput is received outsite the console, in the message handler of DemoGL. CSysConsole uses a cyclic buffer technique to store the console lines. This means that the current line where to write text on moves through the buffer and wraps around at the end to start at the beginning. This way the buffer doesn't have to be copied when the window scrolls up. The console contents depends on the position of the topline visible on screen. When the user scrolls in the console, this topline moves up or down. It depends on this line if a newly logged line is visible or not. When the screen has to be redrawn, the vertexarrays are refilled and rendered, otherwise the already filled arrays are rendered. The cyclic buffer logic can be quite complex at first, especially in the calculations for the scrollbar in the console and which line is visible and which isn't. Everything is commented as best as possible, but it might take a while to get through.
dgl_dlltexture.cpp
This file contains the implementation of the CPixelStore class and the CTexture class. The CTexture class contains all the information for a texture inside DemoGL. All textures created with DemoGL are placed in the texture store. This is an array with pointers to CTexture objects and contains MAXTEXTURES (defined in dgl_dllmain.h) slots and defined in dgl_dllmain.cpp and initialized in DllMain(). This store is not a linked list to keep it as fast as possible because texture access is done very often in a 3D application and it should be smooth and not be blocked by sloppy linked list search routines. The array lets code access a texture object immediately. The CTexture class is mainly ment for storage and is prepared for texture compression and 3D textures. This code is not implemented in v1.3x due to lack of OpenGL 1.2 support in Windows.
dgl_dlltextureutilfuncs.cpp
This file contains all utility functions related to the texture store or the texture object. It contains general texture management functions and texture import routines to convert a loaded block of data into an RGBA bitmap. All functions are prepared, when necessary, for 3D textures and texture compression. As said above, this is not yet implemented. Currently JPEG, TGA BMP and DDS (DirectX Compressed) texture files are supported. When you add more formats, add your import code to this file.
dgl_dlltimelineevent.cpp
This file contains the implementation of the CTimeLineEvent class and the CTLEParameter class. The CTLEParameter class is used to hold script command parameter data. CTimeLineEvent classes are emited objects by the script parser and contain the parsed script commands as data. Each CTimeLineEvent class contains all the script command parameters as CTLEParameter objects in a datastore. All CTimeLineEvent objects are stored in a single linked list, the timeline event store, ordered by TimeSpot Ascending, with a single head pointer, m_gpTimeLineHead, stored in dgl_dllmain.cpp.
dgl_dlltimelineutilfuncs.cpp
This file contains all the functions related to timeline events and the timeline event store. Besides the general add/delete and other similar functions this file contains the largest routine inside DemoGL, the TimeLine Event executor routine DoExecuteTimeLine(). This is in fact the runtime command executor for all objects and all commands. The routine contains a note why the routine isn't split up in a couple of dozen smaller routines. It probably should be recoded with a pre-execution syntax checker to eliminate runtime syntax checking as that's used now. Keep in mind that the massive amount of redundancy has a reason: now every command is flexible in it's own execution without worrying the programmer about other commands sharing this code. Because all code is stored in one routine, the overview is still very good, because nothing is splattered around and because no code should be reused in other commands, splitting up the routine will not bring that of an much advantage.
dgl_dlltoken.cpp
This file contains the implementation of the CToken class. This class is used to store information about a single token recognized by the Lex() routine in dgl_dllscriptfuncs.cpp. Lex will place every part of a script line between two ';' into one CToken object. These objects are stored in an array with file scope inside dgl_dllscriptfuncs.cpp.
dgl_dllutilfuncs.cpp
This file contains the miscellaneous functions that couldn't be stored elsewhere. Most of the functions are helper functions for objects inside DemoGL but are too small to get an own file. In this file are also the file I/O routines which could have been split out of this file and should probably get their own file.

6.2.2. Header Files.
Below are the descriptions of the headerfiles used in the DemoGL sourcecode. Keep in mind that the DemoGL_*.h files are stored in the Include\Distribution directory to keep them separated to make constructions of SDK's easier.
DemoGL_Bass.h
Sound system related header file for distribution purposes. Contains BASS specific flag definitions and structures used by BASS. Use them with certain specific soundsystem related API routines. The reason for the BASS flags and not a conversion of them to DemoGL own flags, and thus keep the stuff more sound library independant, is that when the BASS api changes, the DemoGL own flags have to change as well. To avoid that, these flags and structs are kept in tact, but the amount of them is kept to a minimum. When you port the CSoundSystem class to for example FMod, you should replace this file with an FMod equivalent, which should be too hard.
DemoGL_DLL.h
The DLL include header to use DemoGL inside an application. Keep in mind that all API definitions are copied from the library header files, as this file is not used in the compilation of the library. Keep in mind to copy modifications to API functions or additions to the API functions to this file.
DemoGL_Effect.h
This file is the include file for using CEffect as a baseclass for effectclasses in applications. It's a copy of dgl_dlleffect.h, but now usable for including in user applications. Keep the order of the methods the same in both files. (read: don't alter them, unless you know what you're doing)
DemoGL_Extensions.h
This file contains the definitions of internal constants for all the OpenGL extensions recognized by DemoGL. Allthough as much extensions as possible are added, still some are missing. The constants are used to check if an extension is available and found by DemoGl.
DemoGL_glext.h
This file contains as much extension definitions as known today. All extensions from all popular card vendors are included. This file is useful when you want to bind a functionpointer to an extension or use constants defined by an extension.
dgl_dllbootutilfuncs.h
Header file for dgl_bootutilfuncs.cpp.
dgl_dlldemodat.h
Header file for dgl_dlldemodat.h. Contains CDemoDat class declaration.
dgl_dlleffect.h
Header file for dgl_dlleffectbase.cpp. DemoGL_Effect.h is copied from this file. Contains CEffect class declaration.
dgl_dlleffectstore.h
Header file for dgl_dlleffectstore.cpp. Contains CEffectStore class declaration.
dgl_dllendsystem.h
Header file for dgl_dllendsystem.cpp
dgl_dllextensions.h
Header file for dgl_dllextensions.cpp. Contains CExtensions class declaration.
dgl_dllfifoqueue.h
Header file for dgl_dllfifoqueue.cpp. Contains CFifo class declaration.
dgl_dllguicontrol.h
Header file for dgl_dllguicontrol.cpp. Contains CGuiControl class declaration.
dgl_dllkernel.h
Header file for dgl_dllkernel.cpp.
dgl_dlllayer.h
Header file for dgl_dlllayer.cpp. Contains CLayer class declaration.
dgl_dlllayerutilfuncs.h
Header file for dgl_dlllayerutilfuncs.cpp.
dgl_dlllowleveldebugger.h
Header file for dgl_dlllowleveldebugger.cpp. Contains definitions for the logfile name and how deep the nesting of calllevels can be for indenting in the logfile.
dgl_dllmain.h
Header file for dgl_dllmain.cpp. Contains all global scope constants definitions. Because DemoGL uses a lot of constants instead of hard coded numeric values, most of these values are stored in this file. When you want to tweak or adjust something in DemoGL, this is the place to start.
dgl_dllprogressbar.h
Header file for dgl_dllprogressbar.cpp. Contains CProgressBar class declaration.
dgl_dllscriptfuncs.h
Header file for dgl_dllscriptfuncs.cpp. Contains all command definitions and script related constants.
dgl_dllsoundsystem.h
Header file for dgl_dllsoundsystem.cpp. Contains CSoundSystem, CSoundChannel, CSoundElement, CMP3Element, CMODElement and CSampleElement classes definitions. It also contains the structure definitions for sound system related structures used in the API (and also stored in DemoGL_DLL.h) and internally in the CSoundSystem class.
dgl_dllstartsystem.h
Header file for dgl_dllstartsystem.cpp. Contains the structure definitions for the startup dat structure used in the API calls and internally and the structure to pass parsed commandparameter values for screensaver runtypes.
dgl_dllstartupdialog.h
Header file for dgl_dllstartupdialog.cpp. Contains the about dialog animation related constants.
dgl_dllstdafx.h
Global include file. This file is used to include all .h files. When you add a .h file, simply add it to this file.
dgl_dllsysconsole.h
Header file for dgl_dllsysconsole.cpp. Contains the CSysConsole class declaration and all internal used constants and structure declarations.
dgl_dlltexture.h
Header file for dgl_dlltexture.cpp. Contains the CPixelStore class and CTexture class declarations.
dgl_dlltextureutilfuncs.h
Header file for dgl_dlltextureutilfuncs.cpp.
dgl_dlltimelineevent.h
Header file for dgl_dlltimelineevent.cpp. Contains the CTimeLineEvent and CTLEParameter classes declarations.
dgl_dlltimelineutilfuncs.h
Header file for dgl_dlltimelineutilfuncs.cpp.
dgl_dlltoken.h
Header file for dgl_dlltoken.cpp. Contains the CToken class declaration.
dgl_dllutilfuncs.h
Header file for dgl_dllutilfuncs.cpp.
resource.h
This file is generated by VC++. Do not alter this file. Keep in mind that by definition IDI_ICON is equal to 110 and IDB_APPLOGO is equal to 140.
src_beautifiers.h
A header file with some macro's to easy include TODO, FIXME and QUOTE texts that also appear as a warning in the compiler output. Very handy because you don't have to search the code for all your TODO's. See the header file for usage details and credits.

Top

7. Small last things to think about.

The library is not perfect and perhaps never will be. Even at release point there are several things that should have been changed, removed, recoded or added. They're not. Some inconsistencies are documented here, others are not and the reason they're still in the code is because some line has to be drawn when it's time to quit tweaking and fiddling with the code and release it. One of the benefits of Open Source is that others, with different views on problems can find perhaps much easier solutions to algorithms implemented in the library. These people are especially encouraged to modify, or better: increase the quality, of DemoGL's sourcecode to submit changes and fixes.

DemoGL contains a sloppy, "because you don't know how, you can't crack it" crypting algorithm. With DemoGL this crypting algorithm is exposed to the public, and makes your crypted material decryptable by others. However you can easily adjust the algo to keep your own keys safe, to some point. With DemoGL's sourcecode is also the source of Cryde released, the crypter/packer for DemoGL datafiles. Cryde's sourcecode, an MFC application, is not documented as detailed as DemoGL's code, but still contains lots of comments to get you started. It's easy to add your own crypting algorithm both to Cryde and to DemoGL to keep your data safe for copycats.

The DemoGL version 1.3 will be the first of a perhaps long line of releases based on this initial codebase. We hope you will enjoy the sourcecode and the library's functionality as much as we enjoyed writing the library and cooking up all the ideas and features.

Now, dive into it!

The DemoGL development team.